package io.github.wslxm.springbootplus2.gateway.aopfilter;

import com.alibaba.fastjson.JSON;
import io.github.wslxm.springbootplus2.core.constant.ReqStateConst;
import io.github.wslxm.springbootplus2.core.result.Result;
import io.github.wslxm.springbootplus2.core.result.ResultType;
import io.github.wslxm.springbootplus2.core.utils.date.XjLocalDateTimeUtil;
import io.github.wslxm.springbootplus2.core.utils.threadpool.XjThreadUtil;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysAuthority;
import io.github.wslxm.springbootplus2.manage.sys.model.entity.SysLog;
import io.github.wslxm.springbootplus2.manage.sys.service.SysLogService;
import io.github.wslxm.springbootplus2.starter.robot.service.RobotService;
import io.github.wslxm.springbootplus2.utils.JwtUtil;
import io.github.wslxm.springbootplus2.utils.XjCacheUtil;
import io.github.wslxm.springbootplus2.utils.model.JwtUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;

/**
 * 日志记录
 *
 * @author wangsong
 * @version 1.0.1
 * @mail 1720696548@qq.com
 * @date 2021/1/23 0023 9:17
 */
@SuppressWarnings("all")
@Slf4j
@Component
public class LogFilter {


    @Autowired
    private SysLogService adminLogService;

    @Autowired
    private RobotService robotService;

    /**
     * 服务
     */
    @Value("${spring.application.name:null}")
    private String applicationName;

    /**
     * 日志入库数据记录范围(丰改访问的请求方式将不记录到数据库)
     */
    @Value("${logging.methods:GET,POST,PUT,DELETE}")
    private String methods;

    /**
     * 日志入库需要记录的请求头
     */
    private List<String> headerKeys;

    @Value("${logging.headers:}")
    private void setHeaderKeys(String headers) {
        if (StringUtils.isNotBlank(headers)) {
            headerKeys = Arrays.asList(headers.split(","));
        }
    }

    /**
     * 不需要记录日志的 uri 集, 静态资源, css, js ,路由等等, 只要uri包含以下定义的内容, 将直接跳过改过滤器
     */
    private final List<String> excludeUriList = new ArrayList<>();

    public LogFilter() {
        // 行为验
        excludeUriList.add("/api/open/captcha/gen");
    }

    /**
     * 请求日志收集 / 打印日志到控制台
     * <p>
     * 如需统一日志收集,在此收集内容到统一日志收集器中
     * </P>
     *
     * @param request 改方法为异步执行,request 必须调用方传递,也无法在 proceed中获取
     * @author wangsong
     * @mail 1720696548@qq.com
     * @date 2020/10/28 0028 15:04
     * @version 1.0.1
     */
    public io.github.wslxm.springbootplus2.manage.sys.model.entity.SysLog requestLogCollectAndPrint(ProceedingJoinPoint proceed, HttpServletRequest request) {
        String serverName = request.getServerName();          // 获取域名(服务器路径)
        String referer = request.getHeader("referer");     // 请求来源(发起者当前页面路径)
        String ip = getIpAddress(request);                   // 获取用户真实ip(发起者)
        String uri = request.getRequestURI();                // 请求行中的接口名称
        String method = request.getMethod();                 // 请求方式(get/post)
        String url = request.getRequestURL().toString();     // 获得客户端发送请求的完整url
        String host = request.getRemoteHost();               // 请求的客户机的主机名
        int port = request.getRemotePort();                  // 请求的客户机的端口号
        // 请求的类名
        String className = proceed.getTarget().getClass().getName();
        // 请求的包名
        String packageName = proceed.getTarget().getClass().getPackage().getName();
        // 方法swagger描叙
        MethodSignature signature = (MethodSignature) proceed.getSignature();
        Operation methodAnnotation = signature.getMethod().getAnnotation(Operation.class);
        String methodDesc = null;
        if (methodAnnotation != null) {
            methodDesc = methodAnnotation.summary();
        }
        //类swagger描叙
        Tag classAnnotation = proceed.getTarget().getClass().getDeclaredAnnotation(Tag.class);
        String classDesc = null;
        if (classAnnotation != null) {
            classDesc = classAnnotation.name();
        }

        // 1、如果是文件不调用 proceed.getArgs() 读取数据
        // 修复 Cannot delete C:\Users\wangsong\AppData\Local\Temp\tomcat.9048.xx\work\Tomcat\localhost\ROOT\xxxxx.tmp 临时文件无法删除问题
        Object[] args = null;
        String contentType = request.getContentType();
        String contentTypeFile = "multipart/form-data";
        if (contentType != null && contentType.contains(contentTypeFile)) {
            args = new Object[]{"文件无法解析"};
        } else {
            args = proceed.getArgs();
        }
        Map<String, String> allHeaders = getAllHeaders(request);
        // uri ： 接口  包： packageName,  请求类： 接口+类描叙+接口描叙
        // 记录日志信息
        SysLog log = new SysLog();
        log = setJwtUser(log, request);
        log.setReferer(referer);
        log.setUrl(url);
        log.setUri(uri);
        log.setIp(ip);
        log.setHost(host);
        log.setMethod(method);
        log.setServerName(serverName);
        log.setPort(port + "");
        log.setPackageName(packageName);
        log.setClassName(className);
        log.setClassDesc(classDesc);
        log.setMethodDesc(methodDesc);
        if (allHeaders != null && allHeaders.size() > 0) {
            log.setRequestHeader(JSON.toJSONString(allHeaders));
        }
        // 响应数据
        log.setResponseData(null);
        // 默认失败,接口访问成功后修改为成功
        log.setState(0);
        // 请求数据
        try {
            if (args != null) {
                String jsonParam = JSON.toJSONString(args);
                log.setRequestData(jsonParam);
            }
        } catch (Exception e) {
            log.setRequestData("the request data cannot be parsed");
        }
        // 打印请求日志
        this.printLog(log);
        return log;
    }


    /**
     * 响应日志记录 / 添加日志数据持久化到数据库
     *
     * @param state=0      失败 (默认)  type=1 成功
     * @param obj          返回数据
     * @param executeTime  aop 执行总耗时
     * @param businessTime 业务执行总耗时
     * @param1 future 请求日志记录线程
     * @author wangsong
     * @mail 1720696548@qq.com
     * @date 2020/10/28 0028 20:03
     * @version 1.0.1
     */
    public void responseLogAndSave(io.github.wslxm.springbootplus2.manage.sys.model.entity.SysLog logs, Integer state, Long startTime, Long businessTime, String method, String uri, Object obj) {
        // 代码执行时间
        long executeTime = System.currentTimeMillis() - startTime;
        XjThreadUtil.asyncExecute(() -> {
            // 不记录到数据库+成功的 直接跳出程序
            if (methods.indexOf(method) == -1 && state == ReqStateConst.SUCCESS_INT) {
                return;
            }
            // 指定 uri 排除,不记录日志到数据库
            if (excludeUriList.contains(uri)) {
                return;
            }
            // ========= 以下都是要记录到数据库的 ================
            // 响应的错误信息数据
            logs.setResponseData(this.getLogsResBody(obj));
            // 记录返回数据
            logs.setExecuteTime(executeTime);
            logs.setBusinessTime(businessTime);
            logs.setState(state);

            // 入库数据处理 (防止数据过大)
            if (StringUtils.isNotBlank(logs.getReferer()) && logs.getReferer().length() > 512) {
                logs.setReferer(logs.getReferer().substring(0, 511));
            }
            if (StringUtils.isNotBlank(logs.getUrl()) && logs.getUrl().length() > 512) {
                logs.setUrl(logs.getUrl().substring(0, 511));
            }
            if (StringUtils.isNotBlank(logs.getUri()) && logs.getUri().length() > 256) {
                logs.setUri(logs.getUri().substring(0, 255));
            }
            adminLogService.save(logs);


            // ========= 请求失败的消息发送到群监控 ================
            if (state == ReqStateConst.FAIL_INT) {
                this.sendRobotMsg(logs);
            }
        });
    }


    /**
     * 获取返回数据
     *
     * @param future 线程对象
     * @param obj    响应数据
     */
    private String getLogsResBody(Object obj) {
        // 获取响应数据
        String data = "";
        try {
            if (obj != null) {
                String jsonParam = JSON.toJSONString(obj);
                data = JSON.toJSONString(obj);
            }
            if (data.length() > 65534) {
                data = data.substring(0, 65534);
            }
        } catch (Exception e) {
            data = "无法解析";
        }
        return data;
    }


    /**
     * 日志中记录用户信息
     * <p>
     * 1、 从 JwtUtil 中获取用户信息
     * 2、 把用户信息放入log并返回，如果没有用户信息则用户名为： ╥﹏╥ ，用户id为：0
     * <p/>
     *
     * @return
     */
    private SysLog setJwtUser(SysLog log, HttpServletRequest request) {
        String uri = request.getRequestURI();
        // 获取登录用户信息
        Result<JwtUser> jwtUserR = JwtUtil.getJwtUserR(request, null);
        // 记录日志时不管token是否过期等，是否有效等, 能获取到用户信息表示已登录,否则表示未登录
        if (jwtUserR.getCode().equals(ResultType.SYS_SUCCESS.getValue())) {
            // 已登录
            JwtUser jwtUser = jwtUserR.getData();
            log.setFullName(jwtUser.getFullName());
            log.setUserId(jwtUser.getUserId());
            log.setType(jwtUser.getType());
        } else {
            // 未登录
            log.setFullName("╥﹏╥");
            log.setUserId("0");
            Map<String, SysAuthority> authMap = XjCacheUtil.findAuthAllToMap();
            SysAuthority adminAuthority = authMap.get(uri);
            // Authority adminAuthority = CacheUtil.getAuthMap().get(uri);
            if (adminAuthority != null) {
                log.setType(adminAuthority.getType());
            } else {
                log.setType(-1);
            }
        }
        return log;
    }


    /**
     * 打印请求信息
     *
     * @param adminlog
     * @return void
     * @author wangsong
     * @date 2020/11/9 0009 16:33
     * @version 1.0.1
     */
    private void printLog(SysLog adminlog) {
        // 控制台打印
        log.info("user:[{}] uri:[{}] param:[{}]: ip:[{}]", adminlog.getFullName(), adminlog.getUrl(), adminlog.getRequestData(), adminlog.getIp());
    }


    /**
     * 获取请求地址
     *
     * @param request
     * @return java.lang.String
     * @author wang-song
     * @date 2020/7/14 0014 14:16
     * @version 1.0.1
     */
    private String getIpAddress(HttpServletRequest request) {
        String unknown = "unknown";
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 获取请求头数据
     *
     * @param request
     * @return
     */
    private Map<String, String> getAllHeaders(HttpServletRequest request) {
        if (headerKeys == null) {
            return null;
        }
        Map<String, String> headersMap = new HashMap<>();
        for (String headerKey : headerKeys) {
            String headerValue = request.getHeader(headerKey);
            if (headerValue != null) {
                headersMap.put(headerKey, headerValue);
            }
        }
        return headersMap;
    }


    /**
     * 发送机器人消息
     *
     * @param args   请求参数
     * @param resObj 响应参数
     */
    public void sendRobotMsg(io.github.wslxm.springbootplus2.manage.sys.model.entity.SysLog log) {
        String responseData = log.getResponseData();
        if (StringUtils.isBlank(responseData) || "无法解析".equals(responseData)) {
            return;
        }
        Result result = JSON.parseObject(responseData, Result.class);
        if (result.getCode().equals(Result.success().getCode())) {
            return;
        }
        String content = "【请求错误】 " + applicationName +
                "\n请求时间: " + XjLocalDateTimeUtil.parse(LocalDateTime.now()) +
                "\n操作人Id: " + log.getUserId() +
                "\n操作人: " + log.getFullName() +
                "\n操作ip: " + log.getIp() +
                "\n操作功能: " + log.getClassDesc() + "-" + log.getMethodDesc() +
                "\n请求接口: " + log.getMethod() + ":" + log.getUri() +
                "\n来源: " + log.getReferer() +
                "\n请求参数: " + log.getRequestData() +
                "\n错误码: " + result.getCode() +
                "\n错误信息 : " + result.getMsg() +
                "\n错误详情 : " + (result.getErrorMsg() != null ? result.getErrorMsg() : "-");
        robotService.sendSysMsg(content, result.getCode());
    }
}
